package com.hmall.item.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import com.hmall.common.domain.PageDTO;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.BeanUtils;
import com.hmall.item.domain.po.Item;
import com.hmall.item.domain.po.ItemDoc;
import com.hmall.item.domain.query.ItemPageQuery;
import com.hmall.item.domain.vo.AggregationVo;
import com.hmall.item.mapper.ItemMapper;
import com.hmall.item.service.IItemService;
import lombok.RequiredArgsConstructor;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.SuggestionBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 商品表 服务实现类
 * </p>
 *
 * @author 虎哥
 */
@RequiredArgsConstructor
@Service
public class ItemServiceImpl extends ServiceImpl<ItemMapper, Item> implements IItemService {
    private final RestHighLevelClient restClient;

    @Transactional
    @Override
    public void deductStock(List<OrderDetailDTO> items) {
        String sqlStatement = "com.hmall.item.mapper.ItemMapper.updateStock";
        boolean r = false;
        try {
            r = executeBatch(items, (sqlSession, entity) -> sqlSession.update(sqlStatement, entity));
        } catch (Exception e) {
            throw new BizIllegalException("更新库存异常，可能是库存不足!", e);
        }
        if (!r) {
            throw new BizIllegalException("库存不足！");
        }
    }

    @Override
    public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
        return BeanUtils.copyList(listByIds(ids), ItemDTO.class);
    }

    @Override
    public void rescoverStock(List<OrderDetailDTO> items) {
        String sqlStatement = "com.hmall.item.mapper.ItemMapper.recoverStock";
        Boolean r = false;
        try {
            r = executeBatch(items, (sqlSession, entity) -> {
                sqlSession.update(sqlStatement, entity);
            });
        }catch (Exception e){
            throw new BizIllegalException("更新库存异常，恢复失败!", e);
        }
        if(!r){
            throw new BizIllegalException("恢复库存失败");
        }
    }

    @Override
    public AggregationVo filters(ItemPageQuery query) throws IOException {
        SearchRequest request = new SearchRequest("items");
        //构建复合查询条件
        buildSearch(request,query);
        //构建聚合条件
        request.source().aggregation(AggregationBuilders.terms("brand_agg").field("brand").size(100));
        request.source().aggregation(AggregationBuilders.terms("category_agg").field("category").size(100));

        SearchResponse response = restClient.search(request, RequestOptions.DEFAULT);

        return extractAggInfo(response);
    }

    @Override
    public PageDTO<ItemDoc> search(ItemPageQuery query) throws IOException {
        //构造搜索条件
        SearchRequest request = new SearchRequest("items");
        buildSearch(request,query);

        SearchResponse response = restClient.search(request, RequestOptions.DEFAULT);

        return extractHitsInfo(response,query.getPageSize());
    }

    private PageDTO<ItemDoc> extractHitsInfo(SearchResponse response,Integer pageSize) {
        ArrayList<ItemDoc> itemDTOS = new ArrayList<>();
        PageDTO<ItemDoc> itemDocPageDTO = new PageDTO<>();
        SearchHits hits = response.getHits();
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit searchHit : searchHits) {
            String sourceAsString = searchHit.getSourceAsString();
            ItemDoc itemDoc = JSONUtil.toBean(sourceAsString,ItemDoc.class);
            itemDTOS.add(itemDoc);
        }

        long total = hits.getTotalHits().value;
        long pages = total%pageSize == 0 ? total/pageSize : total/pageSize+1;
        itemDocPageDTO.setList(itemDTOS);
        itemDocPageDTO.setPages(pages);
        itemDocPageDTO.setTotal(total);

        return itemDocPageDTO;
    }

    private void buildSearch(SearchRequest request, ItemPageQuery query) {
        int startIndex = (query.getPageNo()-1)*query.getPageSize();
        //构建查询条件
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if(StrUtil.isNotBlank(query.getKey())){
            boolQuery.must(QueryBuilders.matchQuery("name",query.getKey()));
        }else if(query.getMinPrice() != null || query.getMaxPrice() != null){
            boolQuery.must(QueryBuilders.rangeQuery("price").lte(query.getMaxPrice()).gte(query.getMinPrice()));
        }else if(StrUtil.isNotBlank(query.getCategory())){
            boolQuery.filter(QueryBuilders.termsQuery("category",query.getCategory()));
        }else if(StrUtil.isNotBlank(query.getBrand())){
            boolQuery.filter(QueryBuilders.termsQuery("brand",query.getBrand()));
        }else {
            boolQuery.must(QueryBuilders.matchAllQuery());
        }
        //首先按照评分排序
        request.source().sort("_score",SortOrder.DESC);
        //其次按照排序字段
        if(StrUtil.isBlank(query.getSortBy()) || query.getIsAsc() == null){
            request.source().sort("updateTime",SortOrder.DESC);
        }else {
            request.source().sort(query.getSortBy(),query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC);
        }
        //给广告额外打分
        FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunction = new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                        QueryBuilders.termQuery("isAD",true), ScoreFunctionBuilders.weightFactorFunction(100.0F)),
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                        QueryBuilders.termQuery("isAD",false),ScoreFunctionBuilders.weightFactorFunction(1.0F))
        };
        FunctionScoreQueryBuilder queryBuilder = QueryBuilders.functionScoreQuery(boolQuery,filterFunction);
        request.source()
                .size(query.getPageSize())
                .from(startIndex)
                .query(queryBuilder);

    }

    private AggregationVo extractAggInfo(SearchResponse response) {
        AggregationVo aggregationVo = new AggregationVo();
        ArrayList<String> categorys = new ArrayList<>();
        ArrayList<String> brands = new ArrayList<>();
        Aggregations aggregations = response.getAggregations();
        Terms category_agg = null;
        Terms brand_agg = null;
        if(aggregations != null){
             category_agg = aggregations.get("category_agg");
             brand_agg = aggregations.get("brand_agg");
        }

        if(category_agg != null){
            List<? extends Terms.Bucket> buckets = category_agg.getBuckets();
            if(buckets != null){
                for (Terms.Bucket bucket : buckets) {
                    categorys.add(bucket.getKeyAsString());
                }
            }
        }

        if(brand_agg != null){
            List<? extends Terms.Bucket> buckets = brand_agg.getBuckets();
            if(buckets != null){
                for (Terms.Bucket bucket : buckets) {
                    brands.add(bucket.getKeyAsString());
                }
            }
        }

        aggregationVo.setBrand(brands);
        aggregationVo.setCategory(categorys);

        return aggregationVo;
    }
}
